#include "e2d/ui/PrefebImporter.h"
#include "e2d/ui/Utils.h"

#include "e2d/core/Entity.h"

#ifdef E2D_BUILD_AUDIO 
#include "e2d/core/audio/AudioClips.h"
#endif 

e2d::PrefebCacheFile e2d::PrefebImporter::m_CacheFile;

std::string e2d::PrefebImporter::m_CacheFilePath = ".metamap/prefeb.metamap";
std::string e2d::PrefebImporter::m_PrefebDir = "Prefeb";

bool e2d::PrefebImporter::LoadCache()
{
    fxcc::yml::Serializer serializer;
    FXCC_FAILED(serializer.DecodePath<PrefebCacheFile>(m_CacheFilePath, m_CacheFile));
    return true;
}

bool e2d::PrefebImporter::SaveCache()
{
    fxcc::yml::Serializer serializer;
    FXCC_FAILED(serializer.EncodePath<PrefebCacheFile>(m_CacheFilePath, m_CacheFile));
    return true;
}

e2d::PrefebImporter::PrefebImporter()
{
    m_Name = "_PrefebImporter";
}

std::string e2d::PrefebImporter::GetPrefebPathFromSource(const std::string& source)
{
    const auto& cache = GetCache(source);

    if (cache.m_Prefebs.size())
    {
        return cache.m_Prefebs[0].m_TargetFilePath;
    }
    return "";
}

const e2d::PrefebCache e2d::PrefebImporter::GetCache(const std::string& source)
{
    auto it = m_CacheFile.m_Caches.find(source);
    if (it == m_CacheFile.m_Caches.end())
    {
        ztclog::info(" cannot find the prefeb file data from source %s", source.c_str());
        return  {};
    }
    return it->second;
}

bool e2d::PrefebImporter::ImportFile(const fxcc::File& file)
{
    switch (file.m_FileType)
    {

#ifdef E2D_BUILD_RENDER
    case fxcc::FileType::_Model_:
        return SaveModelPrefeb(file);
#endif
#ifdef E2D_BUILD_AUDIO 
    case fxcc::FileType::_Ogg_:
    case fxcc::FileType::_Wav_:
    case fxcc::FileType::_MP3_:
        return SaveAudioPrefeb(file);

#endif
    default:
        return false;

    }

}

bool e2d::PrefebImporter::SaveModelPrefeb(const fxcc::File& file)
{
    ztclog::info("[%s ]save model prefeb %s", m_Name.c_str(), file.m_RelativePath.c_str());

    auto& metaCacheFile = m_CacheFile.m_Caches[file.m_RelativePath];

    auto scene = e2d::Utils::m_Inst.m_PrefebScene;
    auto fbxFile = std::make_shared<e2d::ModelScene>(file.m_RelativePath);
    auto entity = scene->NewEntity();
    LoadModelData(entity.get(), fbxFile.get(), fbxFile->m_RootNode.get());
    entity->m_Name = file.m_OriginName;
    fs::path targetDir = fs::path(m_LibraryPath) / m_PrefebDir;
    MetaMapIndex metaMapIndex;
    metaMapIndex.SetPrefeb(targetDir.string(), file.m_RelativePath, 0);
    metaCacheFile.m_Prefebs.push_back(metaMapIndex);

    fxcc::yml::Serializer serializer;
    bool flag = serializer.EncodePath<e2d::Entity>(metaMapIndex.m_TargetFilePath, *entity);

    ztclog::info("prebeb entity %s import from %s", file.m_OriginName.c_str(), file.m_RelativePath.c_str());

    return flag;
}

bool e2d::PrefebImporter::SaveAudioPrefeb(const fxcc::File& file)
{

    auto& metaCacheFile = m_CacheFile.m_Caches[file.m_RelativePath];

    auto scene = e2d::Utils::m_Inst.m_PrefebScene;
    auto entity = scene->NewEntity();

    LoadAudioData(entity.get(), file);

    fs::path targetDir = fs::path(m_LibraryPath) / m_PrefebDir;
    MetaMapIndex metaMapIndex;
    metaMapIndex.SetPrefeb(targetDir.string(), file.m_RelativePath, 0);
    metaCacheFile.m_Prefebs.push_back(metaMapIndex);

    fxcc::yml::Serializer serializer;
    bool flag = serializer.EncodePath<e2d::Entity>(metaMapIndex.m_TargetFilePath, *entity);
    return flag;

}

bool e2d::PrefebImporter::LoadAudioData(e2d::Entity* entity, const fxcc::File& file)
{
#ifdef E2D_BUILD_AUDIO
    entity->m_Name = file.m_FileName;

    auto& audioClips = entity->GetOrEmplace<e2d::AudioClips>();

    e2d::AudioClipMap audioMap;
    auto& metaMapIndex = audioMap.m_Index;
    
    fs::path _dir = fs::path(BaseImporter::m_LibraryPath) / fs::path(AudioImporter::m_AudioPath);
    metaMapIndex.SetAudio(_dir.string(), file.m_RelativePath, 0);
    metaMapIndex.SetName(file.m_FileName);

    audioClips.m_Clips.push_back(audioMap);

    return true;
#endif

    return false;
}

bool e2d::PrefebImporter::LoadModelData(e2d::Entity* entity, const e2d::ModelScene* fbxFile, const e2d::Node* node)
{
    ztclog::info("node is %s", node->m_Name.c_str());

    entity->m_Name = node->m_Name;

    fs::path _mesh_p = fs::path(BaseImporter::m_LibraryPath) / fs::path(ModelImporter::m_MeshPath);
    fs::path _mat_p = fs::path(BaseImporter::m_LibraryPath) / fs::path(ModelImporter::m_MaterialPath);
    fs::path _real_mat_p = fs::path(BaseImporter::m_AssertPath) / fs::path(ModelImporter::m_MaterialPath);
    fs::path _bone_p = fs::path(BaseImporter::m_LibraryPath) / fs::path(ModelImporter::m_BonePath);

    auto& transformNode = entity->GetOrEmplace<e2d::TransformNode>();
    transformNode.m_Local = node->m_Offset;

    if (node->m_Meshes.size())
    {
       
        for (auto index : node->m_Meshes)
        {
            const auto& meshData = fbxFile->m_MeshDatas[index];
            if (meshData->HasBones())
            {
                auto& meshRender = entity->GetOrEmplace<e2d::SkinnedMeshRender>();
                {
                    auto& metaMapIndex = meshRender.m_MeshMap.m_Index;
                    metaMapIndex.SetSkinned(_mesh_p.string(), fbxFile->m_Path, index);
                    metaMapIndex.SetName(node->m_Name);
                }

                {
                    auto& metaMapIndex = meshRender.m_MaterialView.m_Index;
                    int matIndex = fbxFile->m_MeshDatas[index]->m_MaterialIndex;
                    auto mat = fbxFile->m_Materials[matIndex];

                    metaMapIndex.SetName(mat->m_Name);
                    metaMapIndex.SetRealMaterial(_real_mat_p.string(), fbxFile->m_Path, index);

   
                }
                {
                    auto& metaMapIndex = meshRender.m_BoneFileMap.m_Index;
                    metaMapIndex.SetBone(_bone_p.string(), fbxFile->m_Path, index);
                    metaMapIndex.SetName(node->m_Name);
                }
            }
            else
            {
                fs::path _p = fs::path(BaseImporter::m_LibraryPath) / fs::path(ModelImporter::m_MeshPath);

                auto& meshRender = entity->GetOrEmplace<e2d::FixedMeshRender>();
                {
                    auto& metaMapIndex = meshRender.m_MeshMap.m_Index;
                    metaMapIndex.SetMesh(_mesh_p.string(), fbxFile->m_Path, index);
                    metaMapIndex.SetName(node->m_Name);
                }

                {
                    auto& metaMapIndex = meshRender.m_MaterialView.m_Index;
                    int matIndex = fbxFile->m_MeshDatas[index]->m_MaterialIndex;
                    auto mat = fbxFile->m_Materials[matIndex];

                    metaMapIndex.SetName(mat->m_Name);
                    metaMapIndex.SetRealMaterial(_real_mat_p.string(), fbxFile->m_Path, index);


                }
            }
        }
    }
    if (node->m_Cameras.size())
    {
        const auto& cameraData = fbxFile->m_Cameras[0];
        auto& cameraView = entity->GetOrEmplace<e2d::CameraView>();
        cameraView.m_ProjData = cameraData->m_ProjData;
    }

    if (node->m_Lights.size())
    {
        const auto& lightData = fbxFile->m_LightDatas[0];
        auto& lightSource = entity->GetOrEmplace<e2d::LightSource>();
        lightSource.m_EffectData = lightData->m_EffectData;
    }

    for (auto childNode : node->m_Childern)
    {
        auto child = entity->NewChild();
        LoadModelData(child.get(), fbxFile, childNode.get());
    }
    return true;
}
